package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
)

// 用于单线程处理的锁
var Mut sync.Mutex

// 本次秒杀的日期
var timedate string
var startrun int32 = 0

type FormIn struct {
	Userid   int    `json:"id"`
	Specname string `json:"specname"`
}

// N 未秒杀成功且不再队列中
// Q 在队列中
func ModelCheck(sid int) gin.H {
	val, err := RedisCon.HGet("Order_Table", strconv.Itoa(sid)).Result()
	if err != nil {
		if err.Error() == "redis: nil" {
			return gin.H{
				"status": false,
				"msg":    "秒杀尚未开始或您不在参与本次秒杀的用户中",
			}
		} else {
			panic(err)
		}
	}
	return gin.H{
		"status": true,
		"spec":   val,
	}
}

func ModelInsert(form FormIn) gin.H {
	if atomic.LoadInt32(&startrun) == 0 {
		return gin.H{
			"status": false,
			"msg":    "秒杀尚未开始或您不在参与本次秒杀的用户中",
		}
	}
	Mut.Lock()
	defer Mut.Unlock()

	val, err := RedisCon.HGet("Order_Table", strconv.Itoa(form.Userid)).Result()
	if err != nil {
		if err.Error() == "redis: nil" {
			return gin.H{
				"status": false,
				"msg":    "秒杀尚未开始或您不在参与本次秒杀的用户中",
			}
		} else {
			panic(err)
		}
	}
	if val == "Q" {
		return gin.H{
			"status": false,
			"msg":    "已经在处理队列中!",
		}
	} else if val != "N" {
		return gin.H{
			"status": false,
			"msg":    "已经参与过秒杀!",
		}
	}
	bs, err := json.Marshal(form)

	pipe := RedisCon.TxPipeline()
	defer pipe.Close()
	if err := pipe.HSet("Order_Table", strconv.Itoa(form.Userid), "Q").Err(); err != nil {
		pipe.Discard()
		panic(err)
	}
	if err != nil {
		pipe.Discard()
		panic(err)
	}
	if err := pipe.XAdd(&redis.XAddArgs{
		Stream: "Order_Que",
		Values: map[string]interface{}{"val": string(bs)},
	}).Err(); err != nil {
		pipe.Discard()
		panic(err)
	}
	if _, err := pipe.Exec(); err != nil {
		pipe.Discard()
		panic(err)
	}
	return gin.H{
		"status": true,
	}
}

// 初始化并将学号加入redis的Order_Table中
func ModelInit(b, e int, tb time.Time, te time.Time, td string) gin.H {
	pipe := RedisCon.TxPipeline()
	defer pipe.Close()

	rows, err := DBSlaveRand().Queryx("SELECT t1.id FROM t_base_user t1 "+
		"LEFT JOIN t_fruit_user_species t2 ON t2.userid = t1.id AND t2.timedate = ? "+
		"WHERE t2.id IS NULL AND t1.namecode >= ? AND t1.namecode < ?", td, b, e)
	if err != nil {
		panic(err)
	}
	if err := pipe.Del("Order_Table").Err(); err != nil {
		pipe.Discard()
		panic(err)
	}
	for rows.Next() {
		var i string
		if err := rows.Scan(&i); err != nil {
			pipe.Discard()
			panic(err)
		}
		if err := pipe.HSet("Order_Table", i, "N").Err(); err != nil {
			pipe.Discard()
			panic(err)
		}
	}
	if _, err := pipe.Exec(); err != nil {
		pipe.Discard()
		panic(err)
	}
	// 定时开始
	go func() {
		<-time.NewTimer(tb.Sub(time.Now())).C // 指定时间执行
		fmt.Println("开始")
		atomic.StoreInt32(&startrun, 1)
	}()
	// 定时结束
	go func() {
		<-time.NewTimer(te.Sub(time.Now())).C // 指定时间执行
		ModelClear()
	}()
	timedate = td
	return gin.H{
		"status": true,
	}
}

func ModelStat() gin.H {
	if num, err := RedisCon.XLen("Order_Que").Result(); err != nil {
		panic(err)
	} else {
		return gin.H{
			"status": true,
			"start":  atomic.LoadInt32(&startrun),
			"quenum": num,
		}
	}
}

func ModelClear() gin.H {
	if err := RedisCon.Del("Order_Que", "Order_Table").Err(); err != nil {
		panic(err)
	}
	atomic.StoreInt32(&startrun, 0)
	return gin.H{
		"status": true,
	}
}

func ModelOperator() {
	for {
		stre, err := RedisCon.XRead(&redis.XReadArgs{
			Streams: []string{"Order_Que", "0"},
			Count:   1,
			Block:   1<<63 - 1, // 阻塞式的队列读取
		}).Result()
		if err != nil {
			panic(err)
		} else if len(stre) != 1 {
			// fmt.Println("ERROR: len(stre) = ", len(stre), "!")
			panic(errors.New(fmt.Sprintf("ERROR: len(stre) = %v !", len(stre))))
		} else if len(stre[0].Messages) != 1 {
			// fmt.Println("ERROR: len(stre[0].Messages) = ", len(stre[0].Messages), " !")
			panic(errors.New(fmt.Sprintf("ERROR: len(stre[0].Messages) = %v !", len(stre[0].Messages))))
		}
		valmap := stre[0].Messages[0].Values
		// fmt.Println(valmap)

		var form FormIn
		if strval, ok := valmap["val"]; !ok {
			panic(errors.New("valmap['val'] !ok !"))
		} else if err := json.Unmarshal([]byte(strval.(string)), &form); err != nil {
			panic(err)
		}

		sussCode := false
		var specid int
		DBMaster().Exec("BEGIN;")
		if err := DBMaster().QueryRow("SELECT id FROM t_fruit_species WHERE name = ? AND rest > 0", form.Specname).Scan(&specid); err != nil {
			DBMaster().Exec("ROLLBACK;")
			// fmt.Println(2, err)
		} else if rst, err := DBMaster().Exec("UPDATE t_fruit_species SET rest = rest - 1 WHERE id = ?;", specid); err != nil {
			DBMaster().Exec("ROLLBACK;")
			// fmt.Println(3, err)
		} else if count, _ := rst.RowsAffected(); count == 0 {
			DBMaster().Exec("ROLLBACK;")
			// fmt.Println(4, count)
		} else if rst, err := DBMaster().Exec("INSERT INTO t_fruit_user_species(userid, specid, timedate) VALUES(?, ?, ?)", form.Userid, specid, timedate); err != nil {
			DBMaster().Exec("ROLLBACK;")
			// fmt.Println(5, err)
			// fmt.Println("INSERT INTO sushe_userroom(roomid, userid) SELECT ?, t1.id FROM goadmin_users AS t1 WHERE t1.username IN ("+sidsStr+");", roomid)
		} else if count, _ := rst.RowsAffected(); count == 0 {
			DBMaster().Exec("ROLLBACK;")
			// fmt.Println(6, count)
			// fmt.Println("INSERT INTO sushe_userroom(roomid, userid) SELECT ?, t1.id FROM goadmin_users AS t1 WHERE t1.username IN ("+sidsStr+");", roomid)
		} else {
			DBMaster().Exec("COMMIT;")
			sussCode = true
		}
		if sussCode {
			pipe := RedisCon.TxPipeline()
			defer pipe.Close()
			if err := pipe.HSet("Order_Table", strconv.Itoa(form.Userid), form.Specname).Err(); err != nil {
				pipe.Discard()
				panic(err)
			}
			if err := pipe.XDel("Order_Que", stre[0].Messages[0].ID).Err(); err != nil {
				pipe.Discard()
				panic(err)
			}
			if _, err := pipe.Exec(); err != nil {
				pipe.Discard()
				panic(err)
			}
		} else {
			// if err := RedisCon.XAdd(&redis.XAddArgs{
			// 	Stream: "Order_QueBad",
			// 	Values: map[string]interface{}{"val": valmap["val"]},
			// }).Err(); err != nil {
			// 	panic(err)
			// }
			if err := RedisCon.HSet("Order_Table", strconv.Itoa(form.Userid), "N").Err(); err != nil {
				panic(err)
			}
			if err := RedisCon.XDel("Order_Que", stre[0].Messages[0].ID).Err(); err != nil {
				panic(err)
			}
		}
	}
}
